Fix a bug in focus tracking when we move between has_pointer_focus and
authorOwen Taylor <otaylor@redhat.com>
Thu, 5 May 2005 00:12:21 +0000 (00:12 +0000)
committerOwen Taylor <otaylor@src.gnome.org>
Thu, 5 May 2005 00:12:21 +0000 (00:12 +0000)
2005-05-04  Owen Taylor  <otaylor@redhat.com>

        * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
        in focus tracking when we move between has_pointer_focus and
        has_focus_window directly. (#109246, Billy Biggs, Niko Tyni
        and others)

        * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
        that could happen in the case of no window manager + keyboard grabs,
        by moving to a more consistent model of when we pay attention
        to mode=NotifyGrab/NotifyUngrab events.

        * docs/focus_tracking.txt: Extensive writeup about how to track
        focus under X11

ChangeLog
ChangeLog.pre-2-10
ChangeLog.pre-2-8
docs/focus_tracking.txt [new file with mode: 0644]
gdk/x11/gdkevents-x11.c
gdk/x11/gdkwindow-x11.h

index 0f41b156d81e4b0ceefaae6a25867ee76983349f..305c35434c72b8462ec338921694d75b70b18e6b 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+2005-05-04  Owen Taylor  <otaylor@redhat.com>
+
+       * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+       in focus tracking when we move between has_pointer_focus and 
+       has_focus_window directly. (#109246, Billy Biggs, Niko Tyni 
+       and others)
+       * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+       that could happen in the case of no window manager + keyboard grabs,
+       by moving to a more consistent model of when we pay attention
+       to mode=NotifyGrab/NotifyUngrab events.
+       
+       * docs/focus_tracking.txt: Extensive writeup about how to track
+       focus under X11.
+
 Wed May  4 13:21:41 2005  Søren Sandmann  <sandmann@redhat.com>
 
        * tests/testcairo.c (draw): Replace cairo_show_surface() uses with
index 0f41b156d81e4b0ceefaae6a25867ee76983349f..305c35434c72b8462ec338921694d75b70b18e6b 100644 (file)
@@ -1,3 +1,18 @@
+2005-05-04  Owen Taylor  <otaylor@redhat.com>
+
+       * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+       in focus tracking when we move between has_pointer_focus and 
+       has_focus_window directly. (#109246, Billy Biggs, Niko Tyni 
+       and others)
+       * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+       that could happen in the case of no window manager + keyboard grabs,
+       by moving to a more consistent model of when we pay attention
+       to mode=NotifyGrab/NotifyUngrab events.
+       
+       * docs/focus_tracking.txt: Extensive writeup about how to track
+       focus under X11.
+
 Wed May  4 13:21:41 2005  Søren Sandmann  <sandmann@redhat.com>
 
        * tests/testcairo.c (draw): Replace cairo_show_surface() uses with
index 0f41b156d81e4b0ceefaae6a25867ee76983349f..305c35434c72b8462ec338921694d75b70b18e6b 100644 (file)
@@ -1,3 +1,18 @@
+2005-05-04  Owen Taylor  <otaylor@redhat.com>
+
+       * gdk/x11/gdkevents-x11.c gdk/x11/gdkwindow-x11.h: Fix a bug
+       in focus tracking when we move between has_pointer_focus and 
+       has_focus_window directly. (#109246, Billy Biggs, Niko Tyni 
+       and others)
+       * gdk/x11/gdkevents-x11.c: Also fix some extremely confusion
+       that could happen in the case of no window manager + keyboard grabs,
+       by moving to a more consistent model of when we pay attention
+       to mode=NotifyGrab/NotifyUngrab events.
+       
+       * docs/focus_tracking.txt: Extensive writeup about how to track
+       focus under X11.
+
 Wed May  4 13:21:41 2005  Søren Sandmann  <sandmann@redhat.com>
 
        * tests/testcairo.c (draw): Replace cairo_show_surface() uses with
diff --git a/docs/focus_tracking.txt b/docs/focus_tracking.txt
new file mode 100644 (file)
index 0000000..c0fcf08
--- /dev/null
@@ -0,0 +1,161 @@
+Notational conventions
+======================
+
+We have a window W that we are tracking events on. Focus
+can be on the following classes of objects
+
+  None        : defined by X protocol
+  PointerRoot : defined by X protocol
+  W           : the window itself
+  Ancestor    : An ancestor of W, including W's root window
+  Descendant  : A descendant of W
+  Other:      : A window that is neither an ancestor or
+                descendant of W
+
+has_pointer(W): the pointer is in W or one of its descendants.
+
+NotifyPointer events
+====================
+
+X sends FocusIn or FocusOut events to W with a detail of NotifyPointer
+in the following transitions, when the pointer is inside W
+
+ Other => Ancestor: FocusIn
+ Ancestor => {Other,None}: FocusOut
+ Ancestor => PointerRoot: FocusOut, then FocusIn
+ {None,W,Descendant,Other} => PointerRoot: FocusIn
+ PointerRoot => Ancestor: FocusOut, then FocusIn
+ PointerRoot => {None,W,Descendant,Other} => FocusOut
+
+[ Ignoring keyboard grabs for the moment ]
+
+Basic focus tracking algorithm
+==============================
+
+Keystroke events are delivered within W if and only if one of two
+predicates hold:
+
+ has_focus_window(W): F==W || F==Descendant
+ has_pointer_focus(W): (F==Ancestor || F==PointerRoot) && has_pointer(W)
+
+These two conditions are mutually exclusive.
+
+has_focus_window(W) is easy to track.
+
+ FocusIn: detail != NotifyInferior: Set has_focus_iwndow
+ FocusOut: detail != NotifyInferior: Clear has_focus_iwndow
+
+has_pointer_focus(W) is harder to track.
+
+We can separate out the transitions from !has_pointer_focus(W) to
+has_pointer_focus(W) into four cases:
+
+ T1: [(F==W || F==Descendant) => F==Ancestor]; has_pointer(W)
+
+ T2: [(F==W || F==Descendant) => F==PointerRoot]; has_pointer(W)
+ T3: [(F==None || F==Other) => (F==PointerRoot || F==Ancestor)];
+   has_pointer(W)
+
+ T4: [!has_pointer(W) => has_pointer(W)]; (F==Ancestor || F==PointerRoot)
+
+All of these can be tracked by watching events on W.
+
+T1:, we get a FocusOut with a mode of Ancestor or Virtual
+  We need to separately track has_pointer(W) to distinguish
+  this from the case where we get these events and !has_pointer(W)
+
+T2, T3: together these are exactly the cases where we get 
+  FocusIn/NotifyPointer.
+
+For T4, we get an EnterNotify with the focus flag set. An
+  EnterNotify with a focus flag set will also be sent if 
+  F==W, so we have to to explicitly test for that case
+  using has_focus_window(W)
+
+
+The transitions from has_pointer_focus(W) to !has_pointer_focus(W)
+are exactly the opposite
+
+ F1: [(F==W || F==Descendant) <= F==Ancestor]; has_pointer(W)
+
+ F2: [(F==W || F==Descendant) <= F==PointerRoot]; has_pointer(W)
+ F3: [(F==None || F==Other) <= (F==PointerRoot || F==Ancestor)];
+   has_pointer(W)
+
+ F4: [!has_pointer(W) <= has_pointer(W)]; (F==Ancestor || F==PointerRoot)
+
+And can be tracked in the same ways:
+
+F1: we get a FocusIn with a mode of Ancestor or Virtual
+  We need to separately track has_pointer(W) to distinguish
+  this from the case we get these events and !has_pointer(W)
+
+F2, F3: together these are exactly the cases where we get 
+  FocusOut/NotifyPointer.
+
+F4: we get an LeaveNotify with the focus flag set. An
+  LeaveNotify with a focus flag set will also be sent if 
+  F==W, so we have to to explicity test for that case
+  using has_focus_window(W).
+
+
+Modifications for keyboard grabs
+================================
+
+The above algorithm ignores keyboard grabs, which also 
+generate focus events, and needs to be modified somewhat
+to take keyboard grabs into effect. The basic idea 
+is that for has_pointer_focus(W)/has_window_focus(W) we track
+them ignoring grabs and ungrabs, and then supplement
+that with another predicate has_focus(W) which pays
+attention to grabs and ungrabs.
+
+Modification 1:
+   
+ When tracking has_pointer_focus(W), ignore all Focus
+ events with a mode of NotifyGrab or NotifyUngrab.
+
+ Note that this means that with grabs, we don't perfectly.
+ track the delivery of keyboard events ... since we think
+ we are getting events in the case where
+
+  has_pointer_focus(W) && !(G == None || G==W || G==descendant)
+
+ But the X protocol doesn't provide sufficient information
+ to do this right... example:
+
+   F=Ancestor, G=None   =>   F=Ancestor, G=Ancestor
+
+ We stop getting events, but receive no notification.
+       
+ The case of no window manager and keyboard grabs is pretty
+ rare in any case.
+
+Modification 2:
+   
+ When tracking has_focus_window(W), ignore all Focus
+ events with a mode of NotifyGrab or NotifyUngrab.
+
+Modification 3: instead of calculating focus as 
+  
+    has_focus_window(W) || has_pointer_focus(W)
+
+  Calculate it as 
+
+    has_focus(W) || has_pointer_focus(W)
+
+  where has_focus(W) is defined as:
+
+   has_focus(W): F==W || F==Descendant || G=W
+
+  Tracking has_focus(W) is done by 
+
+ FocusIn: detail != NotifyInferior, mode != NotifyWhileGrabbed: 
+    set has_focus
+ FocusOut: detail != NotifyInferior, mode != NotifyWhileGrabbed: 
+    clear has_focus
+
+ We still need to track has_focus_window(W) for the T4/F4 
+ transitions.
index f3327fadcb0fcf3640c622df32281f676e136e60..7cc35187caa5b81341573c21d174fc97d4ac4181 100644 (file)
@@ -1218,16 +1218,19 @@ gdk_event_translate (GdkDisplay *display,
         }
       
       /* Handle focusing (in the case where no window manager is running */
-      if (toplevel &&
-         xevent->xcrossing.detail != NotifyInferior &&
-         xevent->xcrossing.focus && !toplevel->has_focus_window)
+      if (toplevel && xevent->xcrossing.detail != NotifyInferior)
        {
-         gboolean had_focus = HAS_FOCUS (toplevel);
+         toplevel->has_pointer = TRUE;
 
-         toplevel->has_pointer_focus = TRUE;
-
-         if (HAS_FOCUS (toplevel) != had_focus)
-           generate_focus_event (window, TRUE);
+         if (xevent->xcrossing.focus && !toplevel->has_focus_window)
+           {
+             gboolean had_focus = HAS_FOCUS (toplevel);
+             
+             toplevel->has_pointer_focus = TRUE;
+             
+             if (HAS_FOCUS (toplevel) != had_focus)
+               generate_focus_event (window, TRUE);
+           }
        }
 
       /* Tell XInput stuff about it if appropriate */
@@ -1312,16 +1315,19 @@ gdk_event_translate (GdkDisplay *display,
         }
       
       /* Handle focusing (in the case where no window manager is running */
-      if (toplevel &&
-         xevent->xcrossing.detail != NotifyInferior &&
-         xevent->xcrossing.focus && !toplevel->has_focus_window)
+      if (toplevel && xevent->xcrossing.detail != NotifyInferior)
        {
-         gboolean had_focus = HAS_FOCUS (toplevel);
-         
-         toplevel->has_pointer_focus = FALSE;
-         
-         if (HAS_FOCUS (toplevel) != had_focus)
-           generate_focus_event (window, FALSE);
+         toplevel->has_pointer = FALSE;
+
+         if (xevent->xcrossing.focus && !toplevel->has_focus_window)
+           {
+             gboolean had_focus = HAS_FOCUS (toplevel);
+             
+             toplevel->has_pointer_focus = FALSE;
+             
+             if (HAS_FOCUS (toplevel) != had_focus)
+               generate_focus_event (window, FALSE);
+           }
        }
 
       event->crossing.type = GDK_LEAVE_NOTIFY;
@@ -1404,10 +1410,25 @@ gdk_event_translate (GdkDisplay *display,
          switch (xevent->xfocus.detail)
            {
            case NotifyAncestor:
-           case NotifyNonlinear:
            case NotifyVirtual:
+             /* When the focus moves from an ancestor of the window to
+              * the window or a descendent of the window, *and* the
+              * pointer is inside the window, then we were previously
+              * receiving keystroke events in the has_pointer_focus
+              * case and are now receiving them in the
+              * has_focus_window case.
+              */
+             if (toplevel->has_pointer &&
+                 xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
+               toplevel->has_pointer_focus = FALSE;
+             
+             /* fall through */
+           case NotifyNonlinear:
            case NotifyNonlinearVirtual:
-             toplevel->has_focus_window = TRUE;
+             if (xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
+               toplevel->has_focus_window = TRUE;
              /* We pretend that the focus moves to the grab
               * window, so we pay attention to NotifyGrab
               * NotifyUngrab, and ignore NotifyWhileGrabbed
@@ -1420,7 +1441,8 @@ gdk_event_translate (GdkDisplay *display,
               * but the pointer focus is ignored while a
               * grab is in effect
               */
-             if (xevent->xfocus.mode != NotifyGrab)
+             if (xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
                toplevel->has_pointer_focus = TRUE;
              break;
            case NotifyInferior:
@@ -1447,15 +1469,31 @@ gdk_event_translate (GdkDisplay *display,
          switch (xevent->xfocus.detail)
            {
            case NotifyAncestor:
-           case NotifyNonlinear:
            case NotifyVirtual:
+             /* When the focus moves from the window or a descendent
+              * of the window to an ancestor of the window, *and* the
+              * pointer is inside the window, then we were previously
+              * receiving keystroke events in the has_focus_window
+              * case and are now receiving them in the
+              * has_pointer_focus case.
+              */
+             if (toplevel->has_pointer &&
+                 xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
+               toplevel->has_pointer_focus = TRUE;
+
+             /* fall through */
+           case NotifyNonlinear:
            case NotifyNonlinearVirtual:
-             toplevel->has_focus_window = FALSE;
+             if (xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
+               toplevel->has_focus_window = FALSE;
              if (xevent->xfocus.mode != NotifyWhileGrabbed)
                toplevel->has_focus = FALSE;
              break;
            case NotifyPointer:
-             if (xevent->xfocus.mode != NotifyUngrab)
+             if (xevent->xfocus.mode != NotifyGrab &&
+                 xevent->xfocus.mode != NotifyUngrab)
                toplevel->has_pointer_focus = FALSE;
            break;
            case NotifyInferior:
index 608c0172c8cd7e0e758634e3e19a5d32df8336cb..f95505aaff5dc535bf421a0a3c8812e10a652410 100644 (file)
@@ -96,9 +96,14 @@ struct _GdkToplevelX11
    */
   guint has_focus : 1;
 
-  /* Set if !window->has_focus_window, but events are being sent to the
-   * window because the pointer is in it. (Typically, no window
-   * manager is running.
+  /* Set if the pointer is inside this window. (This is needed for
+   * for focus tracking)
+   */
+  guint has_pointer : 1;
+  
+  /* Set if the window is a descendent of the focus window and the pointer is
+   * inside it. (This is the case where the window will receive keystroke
+   * events even window->has_focus_window is FALSE)
    */
   guint has_pointer_focus : 1;